﻿using System;
using RaisingStudio.Runtime.CompilerServices;
#if CF_1_0 || CF_2_0 || CF_3_5
using RaisingStudio.Reflection;
using RaisingStudio.Reflection.Emit;
#else
using System.Reflection;
using System.Reflection.Emit;
#endif
using System.Runtime.CompilerServices;


namespace RaisingStudio.Core
{
    public static class Extensions
    {        
        public static bool IsGenericInstanceOf(this Type self, Type type)
        {
            if (!self.IsGenericType)
                return false;

            return self.GetGenericTypeDefinition() == type;
        }

        public static bool IsNullable(this Type self)
        {
            return self.IsValueType && self.IsGenericInstanceOf(typeof(Nullable<>));
        }

        public static bool IsGenericImplementationOf(this Type self, Type type)
        {
            foreach (Type iface in self.GetInterfaces())
                if (iface.IsGenericInstanceOf(type))
                    return true;
            return false;
        }

        public static bool IsAssignableTo(this Type self, Type type)
        {
            return type.IsAssignableFrom(self) ||
                ArrayTypeIsAssignableTo(self, type);
        }

        public static Type GetFirstGenericArgument(this Type self)
        {
            return self.GetGenericArguments()[0];
        }

        public static Type MakeGenericTypeFrom(this Type self, Type type)
        {
            return self.MakeGenericType(type.GetGenericArguments());
        }

        public static Type MakeNullableType(this Type self)
        {
            return typeof(Nullable<>).MakeGenericType(self);
        }

        public static Type GetNotNullableType(this Type self)
        {
            return self.IsNullable() ? self.GetFirstGenericArgument() : self;
        }

        public static MethodInfo GetInvokeMethod(this Type self)
        {
            return self.GetMethod("Invoke", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        }

        public static MethodInfo MakeGenericMethodFrom(this MethodInfo self, MethodInfo method)
        {
            return self.MakeGenericMethod(method.GetGenericArguments());
        }

        public static Type[] GetParameterTypes(this MethodBase self)
        {
            var parameters = self.GetParameters();
            var types = new Type[parameters.Length];

            for (int i = 0; i < types.Length; i++)
                types[i] = parameters[i].ParameterType;

            return types;
        }

        static bool ArrayTypeIsAssignableTo(Type type, Type candidate)
        {
            if (!type.IsArray || !candidate.IsArray)
                return false;

            if (type.GetArrayRank() != candidate.GetArrayRank())
                return false;

            return type.GetElementType().IsAssignableTo(candidate.GetElementType());
        }

        public static void OnFieldOrProperty(this MemberInfo self,
            Action<FieldInfo> onfield, Action<PropertyInfo> onprop)
        {
            switch (self.MemberType)
            {
                case System.Reflection.MemberTypes.Field:
                    onfield((FieldInfo)self);
                    return;
                case System.Reflection.MemberTypes.Property:
                    onprop((PropertyInfo)self);
                    return;
                default:
                    throw new ArgumentException();
            }
        }

        public static T OnFieldOrProperty<T>(this MemberInfo self,
            Func<FieldInfo, T> onfield, Func<PropertyInfo, T> onprop)
        {
            switch (self.MemberType)
            {
                case System.Reflection.MemberTypes.Field:
                    return onfield((FieldInfo)self);
                case System.Reflection.MemberTypes.Property:
                    return onprop((PropertyInfo)self);
                default:
                    throw new ArgumentException();
            }
        }

        public static Type MakeStrongBoxType(this Type self)
        {
            return typeof(StrongBox<>).MakeGenericType(self);
        }
        

        public static Delegate CreateDelegate(System.Type delegateType, object target, System.Reflection.MethodInfo methodInfo)
        {            
            // TODO: *CreateDelegate
            return new DynamicDelegate((new DynamicDelegateHandler(target, methodInfo)).Invoke);
        }

        public static object DynamicInvoke(this Delegate value, params object[] args)
        {
            // TODO: *DynamicInvoke.
            if (value is DynamicDelegate)
            {
                DynamicDelegate dynamicDelegate = value as DynamicDelegate;
                return dynamicDelegate(args);
            }
#if CF_3_5
            else
            {
                DynamicDelegate dynamicDelegate = CreateDelegate(value.GetType(), value.Target, value.Method) as DynamicDelegate;
                return dynamicDelegate(args);
            }
#endif
            return null;
        }

        public delegate object DynamicDelegate(object[] args);
        public class DynamicDelegateHandler : IDisposable
        {
            private object target;
            private System.Reflection.MethodInfo methodInfo;

            public DynamicDelegateHandler(object target, System.Reflection.MethodInfo methodInfo)
            {
                this.target = target;
                this.methodInfo = methodInfo;
            }

            public object Invoke(object[] args)
            {
                if ((args != null) && (args.Length > 0))
                {
                    object[] parameters = new object[args.Length + 1];
                    parameters[0] = target;
                    for (int i = 0; i < args.Length; i++)
                    {
                        parameters[i + 1] = args[i];
                    }
                    return this.methodInfo.Invoke(null, parameters);
                }
                else
                {
                    return this.methodInfo.Invoke(null, new object[] { target });
                }
            }

            #region IDisposable Members

            public void Dispose()
            {
#if CF_1_0 || CF_2_0
                // TODO: **
                // dispose the temp assembly on .net cf 2.0.
                Console.WriteLine(this.methodInfo);
#endif
            }

            #endregion
        }
    }
}
